﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Security.Cryptography;

namespace ResharperKeygen;

public static class Patcher
{
    private static string InstallLocation =
        Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + @"\JetBrains\Installations\";

    private const string PatchsDir = "Patchs";

    public static void Patch()
    {
        if (!Directory.Exists(PatchsDir) || !Directory.Exists(InstallLocation))
        {
            System.Console.WriteLine("There is no crack patch, skip processing");
            return;
        }

        var patchs = new Dictionary<string, string>();
        foreach (var file in Directory.GetFiles(PatchsDir, "JetBrains.Platform.Shell-*.dll"))
        {
            var array = Path.GetFileNameWithoutExtension(file).Split('-');
            if (array.Length != 2) continue;
            patchs.Add(array[1], file);
        }

        if (patchs.Count <= 0)
        {
            Console.WriteLine("Not found patchs");
            return;
        }

        var targets = Directory.GetFiles(InstallLocation, "JetBrains.Platform.Shell.dll",
            SearchOption.AllDirectories);
        if (!targets.Any())
        {
            Console.WriteLine("Not found Installed product");
            return;
        }

        foreach (var target in targets)
        {
            Console.WriteLine("Processing:" + target);
            var assembly = Assembly.Load(File.ReadAllBytes(target));
            var attrs = assembly.GetCustomAttributes<AssemblyMetadataAttribute>();
            var attr = attrs.FirstOrDefault(a => a.Key == "WaveMarketingName");
            if (attr == null)
            {
                Console.WriteLine("Failed to locate target assembly version, skip processing");
                continue;
            }

            if (!patchs.TryGetValue(attr.Value, out var patch))
            {
                Console.WriteLine("Failed to locate target assembly version, actual={attr.Value}, skip processing");
                continue;
            }

            if (IsPatched(patch, target))
            {
                Console.WriteLine("The file has been patched, skip processing");
                continue;
            }

            File.Copy(target, target + ".bak", true);
            File.Copy(patch, target, true);
        }

        Console.WriteLine("Patch processing completed");
    }

    private static bool IsPatched(string source, string target)
    {
        using var md5 = MD5.Create();
        using var s = File.OpenRead(source);
        using var t = File.OpenRead(target);
        return Convert.ToBase64String(md5.ComputeHash(s)) == Convert.ToBase64String(md5.ComputeHash(t));
    }

    private static byte[] PatchPattern =
    [
        0x00, 0x06, 0x0C, 0x08, 0x17, 0x2E, 0x02, 0x08, 0x2A, 0x07, 0x17, 0x58, 0x0B, 0x07, 0x06, 0x8E, 0x69, 0x32,
        0xE4, 0x17, 0x2A, 0x3A, 0x02, 0x28
    ];

    private static byte[] PatchTarget =
    [
        0x00, 0x06, 0x0C, 0x08, 0x17, 0x2E, 0x02, 0x08, 0x2A, 0x07, 0x17, 0x58, 0x0B, 0x07, 0x06, 0x8E, 0x69, 0x32,
        0xE4, 0x16, 0x2A, 0x3A, 0x02, 0x28
    ];

    public static void PatchByPattern()
    {
        if (!Directory.Exists(InstallLocation))
        {
            System.Console.WriteLine("There is no crack patch, skip processing");
            return;
        }

        var targets = Directory.GetFiles(InstallLocation, "JetBrains.Platform.Shell.dll",
            SearchOption.AllDirectories);
        if (!targets.Any())
        {
            Console.WriteLine("Not found Installed product");
            return;
        }

        foreach (var target in targets)
        {
            Console.WriteLine("Processing:" + target);
            var fileData = File.ReadAllBytes(target);
            var end = GetPositionAfterMatch(fileData, PatchPattern);
            var begin = end - PatchTarget.Length;
            if (begin < 0 || begin > fileData.Length - PatchTarget.Length)
            {
                Console.WriteLine(@"not found patch pattern");
            }
            else
            {
                File.Copy(target, target + ".bak", true);
                var fs = File.Open(target, FileMode.Open, FileAccess.ReadWrite);
                fs.Position = begin;
                fs.Write(PatchTarget, 0, PatchTarget.Length);
                fs.Dispose();
                Console.WriteLine("Patch processing completed");
            }
        }
    }

    public static void MatchByPattern()
    {
        if (!Directory.Exists(PatchsDir))
        {
            System.Console.WriteLine("There is no crack patch, skip processing");
            return;
        }

        var targets = Directory.GetFiles(PatchsDir, "JetBrains.Platform.Shell-*.dll",
            SearchOption.TopDirectoryOnly);
        if (!targets.Any())
        {
            Console.WriteLine("Not found Installed product");
            return;
        }

        var count = 0;

        Console.WriteLine("check file pattern match:");

        var color = Console.ForegroundColor;
        foreach (var target in targets)
        {
            //Console.WriteLine("Processing:" + target);
            var targetFileBytes = File.ReadAllBytes(target);
            var index = GetPositionAfterMatch(targetFileBytes, PatchTarget);
            if (index > 0)
            {
                Console.WriteLine($"The file matched,offset={index:X},file={target}");
                count++;
            }
            else
            {
                Console.ForegroundColor = ConsoleColor.Red;
                Console.WriteLine($"The file no matched,offset={index:X},file={target}");
                Console.ForegroundColor = color;
            }
        }

        Console.ForegroundColor = ConsoleColor.Yellow;
        Console.WriteLine($"total files={targets.Length},matched={count},no matched={targets.Length - count}");
        Console.ForegroundColor = color;
    }

    static int GetPositionAfterMatch(byte[] data, byte[] pattern)
    {
        for (int i = 0; i < (data.Length - pattern.Length); i++)
        {
            bool match = true;
            for (int k = 0; k < pattern.Length; k++)
            {
                if (data[i + k] != pattern[k])
                {
                    match = false;
                    break;
                }
            }

            if (match)
            {
                return i + pattern.Length;
            }
        }

        return -1;
    }
}